﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Security.Policy;
using System.Text;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.Zip;

namespace Thor.Common
{
    public class ProviderManager
    {
        #region Fields

        private IProviderRepository _providerRepository = null;
        private List<ProviderAssembly> _assemblies = null;
        private List<ProviderFile> _files = null;
        private string _providerInterfaceName = string.Empty;

        #endregion

        #region Constructors

        public ProviderManager(IProviderRepository providerRepository, string providerInterfaceName)
        {
            _providerRepository = providerRepository;
            _providerInterfaceName = providerInterfaceName;
        }

        #endregion

        #region Properties

        /// <summary>
        /// List of files in the repository.
        /// </summary>
        public List<ProviderFile> Files
        {
            get
            {
                if (_files == null)
                    _files = _providerRepository.FetchFiles();

                return _files;
            }
            set { _files = value; }
        }

        /// <summary>
        /// List of assemblies in the repository.
        /// </summary>
        public List<ProviderAssembly> Assemblies
        {
            get
            {
                if (_assemblies == null)
                {
                    _assemblies = new List<ProviderAssembly>();

                    WriteFilesToDisk();

                    var query = (from file in Files
                                 where file.Name.ToLower().EndsWith(".dll")
                                 select file).ToList();

                    foreach (var item in query)
                    {
                        var assembly = GetAssemblyFromFile(item);

                        if (assembly != null)
                            _assemblies.Add(assembly);
                    }
                }

                return _assemblies;
            }
            set { _assemblies = value; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Inserts a provider.
        /// </summary>
        /// <param name="providerInfo"></param>
        public void Insert(ProviderInfo providerInfo)
        {
            var files = GetFilesFromBytes(providerInfo.Bytes);
            _providerRepository.InsertFiles(files);
        }

        /// <summary>
        /// Updates a provider.
        /// </summary>
        /// <param name="providerInfo"></param>
        public void Update(ProviderInfo providerInfo)
        {
            var files = GetFilesFromBytes(providerInfo.Bytes);
            _providerRepository.UpdateFiles(files);
        }

        /// <summary>
        /// Deletes all providers from the repository.
        /// </summary>
        public void Delete()
        {
            _providerRepository.DeleteFiles();
        }

        /// <summary>
        /// Gets the file from its bytes.
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private List<ProviderFile> GetFilesFromBytes(Byte[] bytes)
        {
            List<ProviderFile> files = new List<ProviderFile>();

            if (bytes != null && bytes.Length > 0)
            {
                MemoryStream inputStream = new MemoryStream();
                inputStream.Write(bytes, 0, bytes.Length);
                inputStream.Seek(0, SeekOrigin.Begin);

                using (ZipInputStream zipInputStream = new ZipInputStream(inputStream))
                {
                    ZipEntry zipEntry = null;

                    while ((zipEntry = zipInputStream.GetNextEntry()) != null)
                    {
                        if (zipEntry.IsFile && !string.IsNullOrEmpty(zipEntry.Name))
                        {
                            ProviderFile file = new ProviderFile();
                            file.Name = zipEntry.Name;

                            MemoryStream outputStream = new MemoryStream();
                            int size = 2048;
                            byte[] data = new byte[2048];

                            while (true)
                            {
                                size = zipInputStream.Read(data, 0, data.Length);

                                if (size > 0)
                                {
                                    outputStream.Write(data, 0, size);
                                }
                                else
                                {
                                    break;
                                }
                            }

                            outputStream.Position = 0;
                            file.Stream = outputStream;

                            files.Add(file);
                        }
                    }
                }
            }

            return files;
        }

        /// <summary>
        /// Gets an assembly based on it name.
        /// </summary>
        /// <param name="name">Name of the assembly.</param>
        /// <returns></returns>
        public Assembly GetAssembly(string name)
        {
            WriteFilesToDisk();

            var query = (from file in this.Files
                         where file.Name == name
                         select file).FirstOrDefault();

            if (query != null)
                return Assembly.Load(query.Bytes);

            return null;
        }

        /// <summary>
        /// Gets an assembly from a file.
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        private ProviderAssembly GetAssemblyFromFile(ProviderFile file)
        {
            ProviderAssembly providerAssembly = new ProviderAssembly();

            Assembly assembly = Assembly.Load(file.Bytes);
            var types = assembly.GetTypes().ToList();

            providerAssembly.Name = file.Name;
            providerAssembly.Path = file.Path;
            providerAssembly.Types = new List<string>();

            if (types != null)
            {
                foreach (var type in types)
                {
                    if (!type.IsAbstract)
                    {
                        var interfaces = type.GetInterfaces();

                        if (interfaces != null && interfaces.Length > 0)
                        {
                            if (interfaces.Any(i => i.FullName == _providerInterfaceName))
                                providerAssembly.Types.Add(type.FullName);
                        }
                    }
                }
            }

            if (providerAssembly.Types == null || providerAssembly.Types.Count == 0)
                return null;

            return providerAssembly;
        }

        /// <summary>
        /// Writes all the files from storage to disk.
        /// </summary>
        /// <remarks>
        /// This was done because of the necessity of having supporting files in the
        /// current app domain's path when trying to load an assembly from storage.
        /// </remarks>
        private void WriteFilesToDisk()
        {
            foreach (var file in this.Files)
            {
                string filePath = AppDomain.CurrentDomain.RelativeSearchPath + @"\" + file.Name;

                if (!File.Exists(filePath))
                {
                    using (FileStream fs = new FileStream(filePath, FileMode.CreateNew))
                    {
                        fs.Write(file.Bytes, 0, file.Bytes.Length);
                        fs.Flush();
                    }
                }
            }
        }

        /// <summary>
        /// Gets a concrete implementation of IAssemblyRepository.
        /// </summary>
        /// <param name="assemblyPath">Full path to the assembly.</param>
        /// <param name="typeName">Full type name of the type in the assembly that implements IAdministratorAccountRepository.</param>
        /// <returns></returns>
        public static IProviderRepository GetRepository(string assemblyPath, string typeName, Dictionary<string, object> repositoryParameters)
        {
            //Load the assembly from the specified path.
            Assembly assembly = Assembly.LoadFile(assemblyPath);

            //Load the type
            Type type = assembly.GetType(typeName);

            //Get the constructor
            Type[] constructorTypes = new Type[1];
            constructorTypes[0] = typeof(Dictionary<string, object>);
            ConstructorInfo constructorInfo = type.GetConstructor(constructorTypes);

            //Invoke the constructor and return a new instance
            if (constructorInfo != null)
                return constructorInfo.Invoke(new object[] { repositoryParameters }) as IProviderRepository;

            return null;
        }

        #endregion
    }
}
